package Redis

import (
	"errors"
	"fmt"
	"gitee.com/xixigroup/sweet-tools/base/Utils"
	"github.com/gomodule/redigo/redis"
	"log"
	"time"
)

/*
	基于redigo封装了常用的redis操作，省略了生成连接，回收连接等繁杂操作
	Usage:
		pool, _ := CreatePool("127.0.0.1:6379", "password", 32, 32, 6, 3600, 0)
		key := "test"
		pool.Set(key, 0)
*/

type RedisPool struct {
	Pool   *redis.Pool
	expire int
}

func CreatePool(addr, password string, MaxIdle, MaxActive, timeout, expire, db int) (*RedisPool, error) {

	var redisPool RedisPool

	poolPtr := &redis.Pool{
		MaxIdle:     MaxIdle,
		MaxActive:   MaxActive,
		Wait:        true,
		IdleTimeout: time.Duration(timeout) * time.Second,
		Dial: func() (redis.Conn, error) {
			c, err := redis.Dial("tcp", addr)
			if err != nil {
				return c, errors.New(fmt.Sprintf("redis connection error: %s", err))
			}
			if password != "" {
				if _, authErr := c.Do("AUTH", password); authErr != nil {
					return c, fmt.Errorf("redis auth password error: %s", authErr)
				}
			}
			//选择分区
			c.Do("SELECT", db)
			return c, nil
		},
	}

	redisPool.Pool = poolPtr
	redisPool.expire = expire

	log.Printf(
		"Redis pool %p initialized (%d/%d)",
		poolPtr, poolPtr.MaxIdle, poolPtr.MaxIdle,
	)
	return &redisPool, nil

}

func (pool *RedisPool) checkPool() {

	poolStatus := pool.Pool.Stats()

	if poolStatus.ActiveCount > pool.Pool.MaxIdle/4*3 {
		log.Printf(
			"Redis pool not enough %p active:%d (%d/%d)",
			pool.Pool, poolStatus.ActiveCount, pool.Pool.MaxIdle-poolStatus.ActiveCount, pool.Pool.MaxIdle,
		)
	}
}

func (pool *RedisPool) Stats() redis.PoolStats {
	return pool.Pool.Stats()
}

func (pool *RedisPool) Set(key string, val interface{}) (string, error) {
	pool.checkPool()

	conn := pool.Pool.Get()
	defer conn.Close()

	res, err := redis.String(conn.Do("set", key, val, "ex", pool.expire))
	return res, err

}

func (pool *RedisPool) SetExp(key string, val interface{}, expire int) (string, error) {
	pool.checkPool()

	conn := pool.Pool.Get()
	defer conn.Close()

	res, err := redis.String(conn.Do("set", key, val, "ex", expire))

	return res, err

}

func (pool *RedisPool) Expire(key string, expire int) (int, error) {

	pool.checkPool()
	conn := pool.Pool.Get()
	defer conn.Close()

	res, err := conn.Do("expire", key, expire)

	if err != nil {
		return 0, err
	}
	row, err := Utils.AssertInt(res)

	return row, err
}

func (pool *RedisPool) Publish(key string, val interface{}) (int, error) {
	pool.checkPool()
	conn := pool.Pool.Get()
	defer conn.Close()
	res, err := conn.Do("PUBLISH", key, val)
	if err != nil || res == nil {
		return 0, err
	}
	row, err := Utils.AssertInt(res)

	return row, err
}

func (pool *RedisPool) Lpush(key string, val interface{}) (int, error) {
	pool.checkPool()
	conn := pool.Pool.Get()
	defer conn.Close()
	res, err := conn.Do("LPUSH", key, val)
	if err != nil {
		return 0, err
	}
	row, err := Utils.AssertInt(res)

	return row, err
}

func (pool *RedisPool) Lpop(key string) (string, error) {
	pool.checkPool()
	conn := pool.Pool.Get()
	defer conn.Close()
	res, err := conn.Do("LPOP", key)
	if err != nil || res == nil {
		return "", err
	}
	byteArr, err := Utils.AssertByteList(res)

	return string(byteArr), err
}

func (pool *RedisPool) Rpush(key string, val interface{}) (int, error) {
	pool.checkPool()
	conn := pool.Pool.Get()
	defer conn.Close()
	res, err := conn.Do("RPUSH", key, val)
	if err != nil {
		return 0, err
	}
	row, err := Utils.AssertInt(res)

	return row, err
}

func (pool *RedisPool) Rpop(key string) (string, error) {
	pool.checkPool()
	conn := pool.Pool.Get()
	defer conn.Close()
	res, err := conn.Do("RPOP", key)
	if err != nil || res == nil {
		return "", err
	}
	byteArr, err := Utils.AssertByteList(res)

	return string(byteArr), err
}

func (pool *RedisPool) Brpop(key string, timeout int) (string, error) {
	pool.checkPool()
	conn := pool.Pool.Get()
	defer conn.Close()
	res, err := conn.Do("BRPOP", key, timeout)
	if err != nil || res == nil {
		return "", err
	}
	byteArr, err := Utils.AssertByteList(res)

	return string(byteArr), err
}

func (pool *RedisPool) Blpop(key string, timeout int) (string, error) {
	pool.checkPool()
	conn := pool.Pool.Get()
	defer conn.Close()
	res, err := conn.Do("BLPOP", key, timeout)
	if err != nil || res == nil {
		return "", err
	}
	byteArr, err := Utils.AssertByteList(res)

	return string(byteArr), err
}

func (pool *RedisPool) Llen(key string) (int, error) {
	pool.checkPool()
	conn := pool.Pool.Get()
	defer conn.Close()
	res, err := conn.Do("LLEN", key)
	if err != nil {
		return 0, err
	}
	length, err := Utils.AssertInt(res)

	return length, err
}

func (pool *RedisPool) Sadd(key string, val interface{}) (int, error) {

	pool.checkPool()
	conn := pool.Pool.Get()
	defer conn.Close()

	res, err := conn.Do("SADD", key, val)
	if err != nil || res == nil {
		return 0, err
	}
	row, err := Utils.AssertInt(res)
	return row, err
}

func (pool *RedisPool) Spop(key string) (string, error) {
	pool.checkPool()
	conn := pool.Pool.Get()
	defer conn.Close()
	res, err := conn.Do("SPOP", key)
	if err != nil || res == nil {
		return "", err
	}
	byteArr, err := Utils.AssertByteList(res)

	return string(byteArr), err
}

func (pool *RedisPool) Smembers(key string) ([]string, error) {

	pool.checkPool()
	conn := pool.Pool.Get()
	defer conn.Close()

	res, err := conn.Do("SMEMBERS", key)
	if err != nil || res == nil {
		return nil, err
	}

	stringArr, err := Utils.AssertBytesArr(res)

	return stringArr, err
}

func (pool *RedisPool) Sismember(key string, val interface{}) (bool, error) {

	pool.checkPool()
	conn := pool.Pool.Get()
	defer conn.Close()

	res, err := conn.Do("SISMEMBER", key, val)
	if err != nil || res == nil {
		return false, err
	}

	v, err := Utils.AssertInt(res)
	if err != nil || v == 0 {
		return false, err
	}
	return true, nil
}

func (pool *RedisPool) Setnx(key string, val interface{}) (bool, error) {

	pool.checkPool()
	conn := pool.Pool.Get()
	defer conn.Close()

	res, err := conn.Do("SETNX", key, val)
	if res == nil {
		return false, err
	}
	v, err := Utils.AssertInt(res)
	if err != nil || v == 0 {
		return false, err
	}
	return true, nil
}

func (pool *RedisPool) Get(key string) (string, error) {

	pool.checkPool()
	conn := pool.Pool.Get()
	defer conn.Close()

	res, err := redis.String(conn.Do("get", key))
	return res, err
}

// Self-increasing value, The incrVal can be negative, it means self-decreasing behavior

func (pool *RedisPool) Incr(key string, incrVal int) (int, error) {

	pool.checkPool()
	conn := pool.Pool.Get()
	defer conn.Close()

	res, err := conn.Do("INCRBY", key, incrVal)

	if err != nil {
		return 0, err
	}
	var num int
	num, err = Utils.AssertInt(res)

	return num, err
}

func (pool *RedisPool) Close() {

	pool.Pool.Close()

}

func (pool *RedisPool) Ping() (string, error) {
	conn := pool.Pool.Get()
	defer conn.Close()

	res, err := redis.String(conn.Do("ping"))

	return res, err
}
